function HanoiView(jqContainer) {
	this.jqContainer = jqContainer;
}

HanoiView.prototype.init = function(controller) {
	this.controller = controller;
	this.initScene();
	this.initMouse();
};

HanoiView.prototype.initScene = function() {
	var width = this.jqContainer.width();
	this.jqContainer.height(width * 0.6);
	var height = this.jqContainer.height();
	this.viewport = {
		width : width,
		height : height
	};

	var camera = new THREE.PerspectiveCamera(45, width / height, 1, 10000);
	camera.position.z = 3200;
	camera.position.y = 1600;
	camera.position.x = 0;
	camera.rotation.x = -Math.PI / 16;
	camera.rotation.y = 0;
	this.camera = camera;

	var scene = new THREE.Scene();

	var usingWebGL = typeof window.WebGLRenderingContext !== 'undefined';

	this.sceneHelper = new HanoiSceneHelper(this.controller.height, usingWebGL);
	var elements = this.sceneHelper.initScene(scene);
	this.rings = elements.rings;
	this.poles = elements.poles;

	var renderer = usingWebGL ? new THREE.WebGLRenderer() : new THREE.CanvasRenderer();
	// renderer = new THREE.CanvasRenderer();
	renderer.setSize(width, height);
	renderer.setClearColor(0xeeeeee, 1);
	this.renderer = renderer;

	this.jqContainer.append(renderer.domElement);

	function animate() {
		setTimeout(function() {
			requestAnimationFrame(animate);
		}, 25);
		renderer.render(scene, camera);
	}
	animate();

	var self = this;
	$(window).on('resize', function() {
		var width = self.jqContainer.width();
		self.jqContainer.height(width * 0.6);
		var height = self.jqContainer.height();
		self.viewport = {
			width : width,
			height : height
		};
		renderer.setSize(width, height);
	});

};

HanoiView.prototype.initMouse = function() {
	var self = this;
	this.dragHelper = new DragHelper(new IntersectionHelper(this.rings, this.camera, function() {
		return self.viewport;
	}), this.poles, $(this.renderer.domElement));

	var controller = this.controller;
	function onRingMoved(ring, newPoleIndex) {
		return controller.onMove(ring.poleIndex, newPoleIndex);
	}

	this.dragHelper.initMouse(onRingMoved);
};

HanoiView.prototype.update = function(state) {
	this.sceneHelper.update(state);
	this.dragHelper.update(state);
};

HanoiView.prototype.onSolved = function() {
	this.dragHelper.unbind();
	setTimeout(function() {
		alert("Solved!");
	}, 100);
};

/**
 * 
 * @param numOfRings
 * @returns
 */
function HanoiSceneHelper(numOfRings, usingWebGL) {
	this.usingWebGL = usingWebGL;

	this.woodColor = 0x966F33;
	this.numOfRings = numOfRings || 3;
	this.cylinderColors = [];
	this.cylinderColors[0] = 0x0000ff;
	this.cylinderColors[1] = 0x00ff00;
	this.cylinderColors[2] = 0xff0000;
	this.cylinderColors[3] = 0xffffff;
}

HanoiSceneHelper.prototype.initScene = function(scene) {
	var poles = [];
	for ( var i = 0; i < 3; i++) {
		var pole = this.getPole(i);
		scene.add(pole);
		poles.push(pole);
	}

	scene.add(this.getBasement());

	var rings = [];
	for ( var i = 1; i <= this.numOfRings; i++) {
		var ring = this.getRing(i);
		rings.push(ring);
		scene.add(ring);
	}

	scene.add(this.getLight(1));
	// scene.add(this.getLight(2));
	var light = new THREE.AmbientLight(0x606060);
	scene.add(light);

	this.rings = rings;

	var result = {
		rings : rings,
		poles : poles
	};
	return result;
};

HanoiSceneHelper.prototype.getLight = function(num) {
	var where = num || 1;

	var pointLight = new THREE.PointLight(0xaaaaaa);

	pointLight.position.y = 2000;
	pointLight.position.z = 2000;
	pointLight.rotation.x = -Math.PI / 2;
	if (where == 1) {
		pointLight.position.x = -1000;
	} else if (where == 2) {
		pointLight.position.x = 1000;
	}

	return pointLight;
};

HanoiSceneHelper.prototype.getPole = function(index) {
	var geometry = new THREE.CylinderGeometry(100, 100, 1200, 20, 1, false);
	var material = new THREE.MeshLambertMaterial({
		color : this.woodColor,
		// wireframe : true,
		overdraw : 0.35,
		ambient : this.woodColor
	});

	var mesh = new THREE.Mesh(geometry, material);
	mesh.position.x = (index - 1) * 1200;
	mesh.position.y = +650;

	return mesh;
};

HanoiSceneHelper.prototype.getBasement = function() {
	var geometry = new THREE.CubeGeometry(4000, 100, 600);
	var material = new THREE.MeshLambertMaterial({
		color : this.woodColor,
		ambient : this.woodColor,
		// wireframe : true,
		overdraw : 0.35
	});

	var mesh = new THREE.Mesh(geometry, material);
	mesh.position.y = 0;

	return mesh;
};

HanoiSceneHelper.prototype.getRing = function(ringIndex) {
	var index = ringIndex - 1;
	var radius = 260 + index * 80;
	var adjustFactor = (this.usingWebGL) ? 2 : 1;
	var geometry = new THREE.TorusGeometry(radius, 100, 20 * adjustFactor, 20 * adjustFactor);
	var material = new THREE.MeshLambertMaterial({
		color : this.cylinderColors[index],
		ambient : this.cylinderColors[index],
		// wireframe : true,
		overdraw : 0.35
	});

	var ring = new THREE.Mesh(geometry, material);
	var position = this.getRingPosition(0, this.numOfRings - ringIndex);
	ring.position.y = position.y;
	ring.position.x = position.x;
	ring.rotation.x = Math.PI / 2;

	ring.ringId = ringIndex;

	return ring;
};

HanoiSceneHelper.prototype.update = function(state) {
	for ( var poleIndex = 0; poleIndex < state.length; poleIndex++) {
		var pole = state[poleIndex];
		for ( var ringIndex = 0; ringIndex < pole.length; ringIndex++) {
			var ringId = pole[ringIndex];
			var ring = this.rings[ringId - 1];
			ring.poleIndex = poleIndex;
			var newPosition = this.getRingPosition(poleIndex, ringIndex);
			ring.position.x = newPosition.x;
			ring.position.y = newPosition.y;
		}
	}
};

HanoiSceneHelper.prototype.getRingPosition = function(poleIndex, ringIndex) {
	return {
		x : 1200 * (poleIndex - 1),
		y : 160 + 200 * (ringIndex)
	};
};

/**
 * 
 * @param rings
 * @param camera
 * @param viewport
 * @returns
 */
function IntersectionHelper(rings, camera, getViewport) {
	this.rings = rings;
	this.camera = camera;
	this.getViewport = getViewport;
};

IntersectionHelper.prototype.intersect = function(x, y) {
	var camera = this.camera;
	var x_pos = (x / this.getViewport().width) * 2 - 1;
	var y_pos = -(y / this.getViewport().height) * 2 + 1;
	var z_pos = 0.5;

	var vector = new THREE.Vector3(x_pos, y_pos, z_pos);

	var projector = new THREE.Projector();
	projector.unprojectVector(vector, camera);
	var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());

	for ( var i = 0; i < this.rings.length; i++) {
		var intersects = raycaster.intersectObject(this.rings[i]);
		if (intersects.length > 0) {
			return this.rings[i];
		}
	}

	return null;
};

/**
 * 
 * @param intersectionHelper
 * @param jqRenderer
 * @returns
 */
function DragHelper(intersectionHelper, poles, jqRenderer) {
	this.intersectionHelper = intersectionHelper;
	this.poles = poles;
	this.jqRenderer = jqRenderer;
}

DragHelper.prototype.update = function(state) {
	this.state = state;
};

DragHelper.prototype.initMouse = function(movedCallback) {
	var startX = 0;
	var startY = 0;
	var draggingObj = null;
	var objStartPos = null;
	var intersectionHelper = this.intersectionHelper;

	var self = this;
	function getOffset() {
		return self.jqRenderer.offset();
	}

	this.jqRenderer.hammer().on('dragstart', function(event) {
		var x = event.gesture.center.pageX - getOffset().left;
		var y = event.gesture.center.pageY - getOffset().top;
		var intersected = intersectionHelper.intersect(x, y);
		if (intersected != null) {
			startX = x;
			startY = y;
			objStartPos = {
				x : intersected.position.x,
				y : intersected.position.y,
				z : intersected.position.z
			};

			if (self.canMove(intersected)) {
				draggingObj = intersected;
			}
		}

		event.gesture.preventDefault();
	});
	this.jqRenderer.hammer().on('drag', function(event) {
		if (draggingObj != null) {
			var xCoeff = 5000 / self.jqRenderer.width();
			var yCoeff = 2600 / self.jqRenderer.height();

			var dx = ((event.gesture.center.pageX - getOffset().left) - startX) * xCoeff;
			var dy = -((event.gesture.center.pageY - getOffset().top) - startY) * yCoeff;

			draggingObj.position.y = objStartPos.y + dy;
			draggingObj.position.x = objStartPos.x + dx;
		}

		event.gesture.preventDefault();
	});
	this.jqRenderer.hammer().on('dragend', function(event) {
		if (draggingObj != null) {
			var moved = false;
			for ( var i = 0; i < self.poles.length; i++) {
				var poleX = self.poles[i].position.x;
				var dx = Math.abs(poleX - draggingObj.position.x);
				if (dx < 300) {
					moved = movedCallback(draggingObj, i);
					break;
				}
			}

			if (!moved) {
				draggingObj.position.x = objStartPos.x;
				draggingObj.position.y = objStartPos.y;
				draggingObj.position.z = objStartPos.z;
			}
		}
		draggingObj = null;

		event.gesture.preventDefault();
	});
};

DragHelper.prototype.canMove = function(ring) {
	var ringId = ring.ringId;

	for ( var poleIndex = 0; poleIndex < this.state.length; poleIndex++) {
		var pole = this.state[poleIndex];
		for ( var ringSlot = 0; ringSlot < pole.length; ringSlot++) {
			if (pole[ringSlot] == ringId) {
				return (pole.length - 1 == ringSlot);
			}
		}
	}

	return false;
};

DragHelper.prototype.unbind = function() {
	this.jqRenderer.unbind();
};